#include "game.h"

#include "simple/support/algorithm/range_wrappers.hpp"
#include "simple/support/vuple.hpp"
#include "simple/support/algorithm/find.hpp"
#include "simple/support/tuple_utils/dig.hpp"

namespace game
{

	using simple::support::dig;

	template <typename C>
	auto& at(C& c, std::size_t i)
	{
		assert(i < c.size());
		return c[i];
	}

	auto& current_field(battle& battle)
	{ return battle.fields[battle.turn%2]; }
	auto& current_field(const battle& battle)
	{ return battle.fields[battle.turn%2]; }
	auto& current_force(battle& battle)
	{ return battle.forces[battle.turn%2]; }
	auto& current_force(const battle& battle)
	{ return battle.forces[battle.turn%2]; }

	auto get_field_lane(battle& battle, unsigned lane)
	{
		return range{ lane, lane+1 }
			* battle.field_size.x()
			+ current_field(battle).begin()
		;
	}

	// TODO: move to support library
	template <typename F, typename... I,
		std::enable_if_t<not (simple::support::is_template_instance_v<simple::support::vuple, I> && ...)>* = nullptr>
	constexpr auto vuple_iota(unsigned step, unsigned size, F f, I... i)
	{
		assert(size != 0);
		if(size == 1) return f(i...);
		else return vuple_iota(step, size-1, f, simple::support::vuple{i, i+step}...);
	}

	template <typename F, typename... I,
		std::enable_if_t<(simple::support::is_template_instance_v<simple::support::vuple, I> && ...)>* = nullptr,
		std::enable_if_t<((std::tuple_size_v<I> < 4) && ...)>* = nullptr>
	constexpr auto vuple_iota(unsigned step, unsigned size, F f, I... i)
	{
		assert(size != 0);
		if(size == 1) return f(i...);
		else
		{
			auto step_vuple = [&](auto vi)
			{
				return std::apply([&](auto first, auto... rest)
				{
					return simple::support::vuple{first, first+step, (rest+step)...};
				}, vi);
			};
			return vuple_iota(step, size-1, f, step_vuple(i)...);
		}
	}

	template <typename F, typename... I,
		std::enable_if_t<(simple::support::is_template_instance_v<simple::support::vuple, I> && ...)>* = nullptr,
		std::enable_if_t<not ((std::tuple_size_v<I> < 4) && ...)>* = nullptr>
	constexpr auto vuple_iota(unsigned, unsigned size, F f, I... i)
	{
		assert(size != 0);
		if(size == 1) return f(i...);
		else assert(false && "vuple_iota maximum size exceeded");

		return decltype(f(i...)){}; // oof, supposed to be unreachable
	}

	bool check_match_at(const battle&, field_t::iterator, color)
	{
		return false;
	}

	template <std::size_t i, typename Vuple>
	[[nodiscard]]
	constexpr decltype(auto) diagonal_get(Vuple&& v)
	{
		return dig<
			simple::support::meta::fill_index_sequence_t<i,
			simple::support::vuple_depth<std::remove_cvref_t<Vuple>>::value>
		>(std::forward<Vuple>(v));
	}

	//
	//            < >
	// | | | | | | | | | |
	//             ^     ^
	// 			 pivot  end
	//
	struct pingponger
	{
		unsigned end;
		unsigned pivot;
		unsigned direction = 0;
		int offset = 0;

		std::optional<int> next()
		{
			if(unsigned(offset) == std::max(pivot, end-pivot))
				return std::nullopt;

			int ret = direction == 0 ? offset : -offset-1; // -1 one a la reverse_iterator

			if(unsigned(offset) < std::min(pivot, end-pivot))
			{
				++direction;
				if(direction == 2)
				{
					direction = 0;
					++offset;
				}
			}
			else
			{
				++offset;
			}

			return ret;
		}
	};

	// welp this is a codefull @.@
	guy operator+(guy g, unsigned offset) { g.reference += offset; return g; } // tis'll come back to bite me innit
	field_t::iterator world::spawn()
	{
		assert( current_battle );
		auto force = current_force(*current_battle);

		const unsigned guy_type_count = std::count_if(force.guys.begin(), force.guys.end(),
			[](auto&& x) { return dig<force::counter>(x) != force::counter{}; });

		auto random_guy = random::distribution::naive_int{guy_type_count}(rng);
		pingponger guy_pingpong{guy_type_count, random_guy, rng()%2 * (random_guy != 0)};

		field_t::iterator spawned = current_field(*current_battle).end();
		std::optional<int> guy_offset = std::nullopt;
		while(spawned == current_field(*current_battle).end() && (guy_offset = guy_pingpong.next()))
		{
			auto guy_info = simple::support::find_if( // find_nth_if? find_if_nth?? next(filter_view(...), n)???
				// TODO: support::vrange, make_vrange? zip_range??
				simple::support::vbegin(force.guys, guy_config),
				simple::support::vend(force.guys, guy_config),
				[i = (random_guy + *guy_offset)](auto&& guy) mutable {
					if(dig<force::counter>(guy) != force::counter{})
					{
						if(i == 0) return true;
						else --i;
					}
					return false;
				}
			);
			assert(not reaches(guy_info, simple::support::vend(force.guys, guy_config)));

			const auto guy_color = dig<color>(*guy_info);

			guy candidate
			{
				guy_type{unsigned(get<0>(guy_info) - force.guys.begin())}, // guy type is index into force.guys array (we know it's positive by now compiler ugh!)
				guy_color,
				get<guy_attributes&>(*guy_info).defaults, // variables
			};
			const auto guy_size = std::get<guy_attributes&>(*guy_info).size;

			// constrain lane bounds based on size
			const auto lane_slots = current_battle->field_size.y() - (guy_size.y() - 1);
			random::distribution::naive_int random_lane{lane_slots};

			// pick a random lane within bounds
			auto lane_index = random_lane(rng);
			auto lane = get_field_lane(*current_battle, lane_index);

			assert(simple::geom::to_conjunction(guy_size != unsigned2::zero()));

			// make shaped lane iterator based on guy size
			spawned =
			vuple_iota(current_battle->field_size.x(), guy_size.y(), [&](auto column, auto end, auto candidate)
			{ return vuple_iota(1, guy_size.x(), [&](auto block, auto candidate)
			{
				pingponger lane_pingpong{lane_slots, lane_index, rng()%2 * (lane_index != 0)};

				while(auto offset = lane_pingpong.next())
				{
					auto lane_offset = *offset * simple::support::distance(lane);
					// auto lane_offset = *offset * state.field_size.x(); // got bitten by this negative promoted to bajilion, I have no solution to this... i mean promotion rules are totally broken, that's no surprise, but still... just use int for everything you say... that's a cope though...

					// find the non empty spot in the lane,
					auto start = block + lane_offset;
					auto non_empty = simple::support::find_if(start, end + lane_offset, [](auto guy){ return guy != std::nullopt; });
					if(non_empty == start)
						continue; // full to the brim

					auto candidate_position = non_empty-1;
					if((guy_size == unsigned2::one() && check_match_at(*current_battle, diagonal_get<0>(candidate_position), guy_color)))
						continue; // would match

					// all ok
					*candidate_position = candidate;
					--dig<force::counter>(*guy_info);
					return diagonal_get<0>(candidate_position);
				}

				return current_field(*current_battle).end();
			}, simple::support::vuple{column}, simple::support::vuple{candidate});
			}, lane.begin(), lane.end(), candidate);

		}

		return spawned;
	}

	bool world::valid_field_position(field_t::iterator p)
	{
		assert(current_battle);
		const auto& field = current_field(*current_battle);
		assert( field.begin() <= p && p <= field.end() );
		return p != field.end();
	}

	void world::recover(unsigned force, const guy&, outcome)
	{
	}

	void world::reinforce()
	{
	}

	void world::end_turn()
	{
		// TODO
	}

	void world::perform(action)
	{
		// TODO
	}

} // namespace game
